//////////////////////////////////////////////////////////////////////////

#ifndef _BOOSTCLIENTCONNECTIONIMPL_H_
#include "boostclientconnectionimpl.h"
#endif

//////////////////////////////////////////////////////////////////////////

#ifndef _INNERPACKET_H_
#include "innerpacket.h"
#endif

#ifndef _TIMER_H_
#include "timer.h"
#endif

#include <boost/bind.hpp>
#include <iostream>

//////////////////////////////////////////////////////////////////////////


BoostClientConnectionImpl::BoostClientConnectionImpl()
: m_TCPSocket(m_IOService),
  m_UDPSocket(m_IOService),
  m_Started(false),
  m_UDPConfirmed(false)
{
}


//////////////////////////////////////////////////////////////////////////


BoostClientConnectionImpl::~BoostClientConnectionImpl()
{
}


//////////////////////////////////////////////////////////////////////////


void BoostClientConnectionImpl::SendTCPInner( const Packet& _data )
{
    // Send data through TCP
    m_TCPSocket.async_send( boost::asio::buffer( _data.Data(), _data.Size() ),
        boost::bind( &BoostClientConnectionImpl::HandleSendTCP, this,
        boost::asio::placeholders::error,
        boost::asio::placeholders::bytes_transferred ) );

    Run();
}


//////////////////////////////////////////////////////////////////////////


void BoostClientConnectionImpl::SendUDPInner( const Packet& _data )
{
    // Send data through UDP
    m_UDPSocket.async_send_to( boost::asio::buffer( _data.Data(), _data.Size() ),
        m_UDPEndPoint,
        boost::bind( &BoostClientConnectionImpl::HandleSendUDP,
        this,
        boost::asio::placeholders::error,
        boost::asio::placeholders::bytes_transferred ) );

    Run();
}


//////////////////////////////////////////////////////////////////////////


void BoostClientConnectionImpl::SendTCP( const Packet& _data )
{
    // Create inner packet from user packet
    InnerPacket innerPacket(_data, InnerPacket::REGULAR);
    innerPacket.PackCode();

    // Send inner packet
    SendTCPInner(innerPacket);
}


////////////////////////////////////////////////////////////////////////////////


void BoostClientConnectionImpl::SendUDP( const Packet& _data )
{
    // Ensure that UDP is confirmed before sending
    if ( !IsUDPConfirmed() )
        ConfirmUDP();

    // Create inner packet
    InnerPacket innerPacket(_data, InnerPacket::REGULAR);
    innerPacket.PackCode();

    // And send it
    SendUDPInner(innerPacket);
}



////////////////////////////////////////////////////////////////////////////////


bool BoostClientConnectionImpl::IsUDPConfirmed()
{
    return m_UDPConfirmed;
}


////////////////////////////////////////////////////////////////////////////////


void BoostClientConnectionImpl::ConfirmUDP()
{
    // Confirm UDP connection
    while ( !IsUDPConfirmed() )
    {
        // Until UDP connection between server and client is not confirmed,
        // send confirmation packets.
        // Confirmation packet consists of confirmation string, which was
        // received from server via TCP

        // Create confirmation packet
        InnerPacket confirm(m_Confirmation, InnerPacket::CONNECT);
        confirm.PackCode();

        // Send packet
        SendUDPInner(confirm);

        // Wait
        Timer::Sleep(100);
    }
}


////////////////////////////////////////////////////////////////////////////////


void BoostClientConnectionImpl::StartReceiveTCP()
{
    // Recv data through TCP
    m_TCPSocket.async_receive( boost::asio::buffer(m_RecvBufferTCP, MAX_BUFFER_SIZE),
        boost::bind( &BoostClientConnectionImpl::HandleRecvTCP, this,
        boost::asio::placeholders::error,
        boost::asio::placeholders::bytes_transferred ) );

    Run();
}


////////////////////////////////////////////////////////////////////////////////


void BoostClientConnectionImpl::StartReceiveUDP()
{
    // Recv data through UDP
    m_UDPSocket.async_receive_from( boost::asio::buffer(m_RecvBufferUDP, MAX_BUFFER_SIZE),
        m_UDPEndPoint,
        boost::bind( &BoostClientConnectionImpl::HandleRecvUDP,
        this,
        boost::asio::placeholders::error,
        boost::asio::placeholders::bytes_transferred ) );

    Run();
}


////////////////////////////////////////////////////////////////////////////////


void BoostClientConnectionImpl::HandleSendTCP( const error_code& _error,
                                        size_t _bytesTransferred )
{
    if (!_error)
        ConnectionImpl::HandleSendTCP();
    else
        std::cout << "ERROR SEND TCP: " << _error.message() << std::endl;
}


////////////////////////////////////////////////////////////////////////////////



void BoostClientConnectionImpl::HandleSendUDP( const error_code& _error, size_t _bytesTransferred )
{
    if (!_error)
        ConnectionImpl::HandleSendUDP();
    else
        std::cout << "ERROR SEND UDP: " << _error.message() << std::endl;
}


////////////////////////////////////////////////////////////////////////////////


void BoostClientConnectionImpl::HandleRecvTCP( const error_code& _error, size_t _bytesTransferred )
{
    if (!_error)
    {
        // Compose inner packet from data buffer
        InnerPacket innerPacket(m_RecvBufferTCP, _bytesTransferred);

        // Get its code
        switch ( innerPacket.UnPackCode() )
        {
        // Regular packet with server data
        case InnerPacket::REGULAR:
            m_RecvPacketTCP = innerPacket;
            ConnectionImpl::HandleRecvTCP();
            break;

        // Confirm UDP connection
        case InnerPacket::CONNECT:
            m_Confirmation = innerPacket;
            break;

        // Get confirmation from server
        case InnerPacket::CONFIRM:
            m_UDPConfirmed = true;
            break;

        default:
            assert(!"Invalid packet type");
            break;
        }

        // Start receive TCP again
        StartReceiveTCP();
    }
    else
        std::cout << "ERROR RECV TCP: " << _error.message() << std::endl;
}


////////////////////////////////////////////////////////////////////////////////


void BoostClientConnectionImpl::HandleRecvUDP( const error_code& _error, size_t _bytesTransferred )
{
    if (!_error)
    {
        // Compose inner packet from data buffer
        InnerPacket innerPacket(m_RecvBufferUDP, _bytesTransferred);

        // Get its code
        switch ( innerPacket.UnPackCode() )
        {
        // Regular packet with server data
        case InnerPacket::REGULAR:
            m_RecvPacketUDP = innerPacket;
            ConnectionImpl::HandleRecvUDP();
            break;

        default:
            assert(!"Invalid packet type");
            break;
        }

        // Start receive UDP again
        StartReceiveUDP();
    }


    // Some strange bugs...
    else if (_error.value() == 10022 || _error.value() == 10061)
        StartReceiveUDP();
    else
    {
        assert(!"Unknown UDP receive error");
        std::cout << "ERROR RECV UDP: " << _error.message() << std::endl;
    }
}


////////////////////////////////////////////////////////////////////////////////


void BoostClientConnectionImpl::Accept()
{
    // Empty implementation here, Accept() is for server only, not for client
}


////////////////////////////////////////////////////////////////////////////////


void BoostClientConnectionImpl::Connect( const char* _address )
{
    // If successfully connected over TCP
    if ( EstablishTCP(_address) )
    {
        // Start receiving TCP
        StartReceiveTCP();

        // Also establish UDP connection
        EstablishUDP(_address);
    }
}


////////////////////////////////////////////////////////////////////////////////


bool BoostClientConnectionImpl::EstablishTCP( const char* _address )
{
    // Resolve address
    tcp::resolver resolver(m_IOService);
    tcp::resolver::query query(_address, m_PortStr);
    tcp::resolver::iterator endpoint_iterator = resolver.resolve(query);
    tcp::resolver::iterator end;

    error_code error = boost::asio::error::host_not_found;
    while (error && endpoint_iterator != end)
    {
        m_TCPSocket.close();

        // Connect to server
        m_TCPSocket.connect(*endpoint_iterator, error);

        ++endpoint_iterator;
    }

    if (!error)
    {
        // No errors
        return true;
    }
    else
    {
        // Error
        std::cout << "ERROR CONNECT: " << error.message() << std::endl;
        return false;
    }
}


////////////////////////////////////////////////////////////////////////////////


void BoostClientConnectionImpl::EstablishUDP( const char* _address )
{
    // Resolve server address
    udp::resolver resolver(m_IOService);
    udp::resolver::query query( udp::v4(), _address, m_PortStr );
    m_UDPEndPoint = *resolver.resolve(query);
    m_UDPSocket.open( udp::v4() );

    // Start receive over UDP
    StartReceiveUDP();
}


////////////////////////////////////////////////////////////////////////////////


void BoostClientConnectionImpl::Close()
{
    m_TCPSocket.close();
}


////////////////////////////////////////////////////////////////////////////////


void BoostClientConnectionImpl::Run()
{
    if (!m_Started)
        m_Thread = boost::thread( boost::bind(&BoostClientConnectionImpl::ThreadProc, this) );
}


////////////////////////////////////////////////////////////////////////////////


void BoostClientConnectionImpl::ThreadProc()
{
    m_Started = true;
    m_IOService.run();
    m_Started = false;
}


////////////////////////////////////////////////////////////////////////////////
